﻿using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Web;
using System.Xml.Linq;
using HTB.DevFx.Core;
using HTB.DevFx.Utils;
using Maple.Weixin.MP.Config;
using Maple.Weixin.MP.Cryptography;
using Maple.Weixin.MP.Entities;
using Maple.Weixin.MP.Entities.JsonResult;
using Maple.Weixin.MP.Entities.Menus;
using Maple.Weixin.MP.Entities.Messages;
using Maple.Weixin.MP.Exceptions;
using Maple.Weixin.MP.Helpers;
using Maple.Weixin.MP.OAuth;
using Maple.Weixin.MP.QRCode;

namespace Maple.Weixin.MP
{
    public class WeixinService : ServiceBase<IWeixinServiceSetting>, IWeixinService
    {
        private IAccessTokenContainer container;
        private IMessageBus messageBus;
        private WXBizMsgCrypt wxBizMsgCrypt;

        protected override void OnInit()
        {
            base.OnInit();

            container = ObjectService.CreateObject<IAccessTokenContainer>(new object[] {Setting.AppId, Setting.AppSecret});
            messageBus = ObjectService.GetObject<IMessageBus>();
            wxBizMsgCrypt = new WXBizMsgCrypt(Setting.Token, Setting.EncodingAESKey, Setting.AppId);
        }

        public string Token {
            get { return string.IsNullOrEmpty(Setting.Token) ? "Weixin" : Setting.Token; }
        }

        public string EncodingAESKey {
            get { return string.IsNullOrEmpty(Setting.EncodingAESKey) ? "" : Setting.EncodingAESKey; }
        }

        public IMessageBus MessageBus {
            get { return messageBus; }
        }

        #region Implementation of IWeixinService

        /// <summary>
        /// 检验消息的真实性，并且获取解密后的明文。
        /// </summary>
        /// <param name="signature">签名，对应URL中的msg_signature。</param>
        /// <param name="timestamp">时间戳，对应URL中的timestamp。</param>
        /// <param name="nonce">随机串，对应URL中的nonce。</param>
        /// <param name="postData">密文，对应POST的数据。</param>
        /// <param name="message">解密后的原文，当return返回0时有效。</param>
        /// <returns>解密成功返回0，否则为错误码。</returns>
        public WXBizMsgCryptErrorCode DecryptMessage(string signature, string timestamp, string nonce, string postData,
            ref string message) {
                return
                    (WXBizMsgCryptErrorCode)
                        wxBizMsgCrypt.DecryptMessage(signature, timestamp, nonce, postData, ref message);
        }

        /// <summary>
        /// 将回复用户的消息加密打包。
        /// </summary>
        /// <param name="replyMessage">回复用户的消息，XML格式的字符串。</param>
        /// <param name="timestamp">时间戳。</param>
        /// <param name="nonce">随机串，可以自己生成，也可以用URL中的nonce。</param>
        /// <param name="encryptMessage">加密后的密文。包括msg_signature, timestamp, nonce, encrypt的XML格式字符串。</param>
        /// <returns>成功返回0，否则返回错误码。</returns>
        public WXBizMsgCryptErrorCode EncryptMessage(string replyMessage, string timestamp, string nonce,
            ref string encryptMessage) {
                return
                    (WXBizMsgCryptErrorCode)
                        wxBizMsgCrypt.EncryptMessage(replyMessage, timestamp, nonce, ref encryptMessage);
        }

        /// <summary>
        /// 获取内部的AccessToken容器。
        /// </summary>
        public IAccessTokenContainer AccessTokenContainer { get { return container; } }

        /// <summary>
        /// 获取微信签名。用于首次验证。
        /// </summary>
        /// <param name="timestamp">时间戳。</param>
        /// <param name="nonce">随机数。</param>
        /// <returns>根据参数计算的签名。</returns>
        public string GetSignature(string timestamp, string nonce) {
            var sign = WeixinHelper.GetSignature(timestamp, nonce, Token);
            return sign;
        }

        /// <summary>
        /// 获取access_token。
        /// </summary>
        /// <returns>获取到的access_token。</returns>
        public string GetAccessToken(bool getNewToken = false) {
            return container.GetAccessToken(getNewToken);
        }

        /// <summary>
        /// 创建OAuth认证的起始Url。
        /// </summary>
        /// <param name="redirectUrl"></param>
        /// <param name="state"></param>
        /// <param name="scope"></param>
        /// <returns></returns>
        public string CreateAuthorizeUrl(string redirectUrl, string state, OAuthScope scope)
        {
            return WeixinHelper.GetOAuthUrl(this.Setting.AppId, redirectUrl, state, scope);
        }

        /// <summary>
        /// 创建OAuth认证的起始Url。
        /// </summary>
        /// <param name="appid">公众号的APPID。</param>
        /// <param name="redirectUrl">请求之后的回调地址。</param>
        /// <param name="state">重定向之后的原样返回参数，必须为a-zA-Z0-9。</param>
        /// <param name="scope">范围的枚举值。</param>
        /// <returns>对应请求的Url地址。</returns>
        public string CreateAuthorizeUrl(string appid, string redirectUrl, string state, OAuthScope scope)
        {
            return WeixinHelper.GetOAuthUrl(appid, redirectUrl, state, scope);
        }

        /// <summary>
        /// 使用code获取OAuth认证的AccessToken。如果使用的是snsapi_base，那到这里就可以拿到openid了。
        /// </summary>
        /// <param name="code">请求到的code值。</param>
        /// <returns>取得的AccessToken结果。</returns>
        public OAuthAccessTokenResult GetOAuthAccessToken(string code)
        {
            return GetOAuthAccessToken(this.Setting.AppId, this.Setting.AppSecret, code);
        }

        /// <summary>
        /// 使用code获取OAuth认证的AccessToken。如果使用的是snsapi_base，那到这里就可以拿到openid了。
        /// </summary>
        /// <param name="appid">公众号的APPID。</param>
        /// <param name="secret">公众号的APPSECRET。</param>
        /// <param name="code">请求到的code值。</param>
        /// <returns>取得的AccessToken结果。</returns>
        public OAuthAccessTokenResult GetOAuthAccessToken(string appid, string secret, string code)
        {
            var accessTokenUrl = WeixinHelper.GetOAuthAccessTokenUrl(appid, secret, code);
            var result = HttpHelper.Get(accessTokenUrl);
            return JsonHelper.FromJson<OAuthAccessTokenResult>(result, true);
        }

        /// <summary>
        /// 刷新AccessToken。
        /// </summary>
        /// <returns>取得的AccessToken结果。</returns>
        public OAuthAccessTokenResult RefreshAccessToken(string refreshToken)
        {
            return RefreshAccessToken(this.Setting.AppId, refreshToken);
        }

        /// <summary>
        /// 刷新AccessToken。
        /// </summary>
        /// <param name="appid">公众号的APPID。</param>
        /// <param name="refreshToken">得到的refreshToken参数。</param>
        /// <returns>取得的AccessToken结果。</returns>
        public OAuthAccessTokenResult RefreshAccessToken(string appid, string refreshToken)
        {
            var refreshTokenUrl = WeixinHelper.GetOAuthRefreshTokenUrl(appid, refreshToken);
            var result = HttpHelper.Get(refreshTokenUrl);
            return JsonHelper.FromJson<OAuthAccessTokenResult>(result, true);
        }

        /// <summary>
        /// 根据openid获取用户信息。
        /// </summary>
        /// <param name="accessToken">授权接口调用凭证。</param>
        /// <param name="openid">用户唯一标识。</param>
        /// <param name="lang">语言，目前支持简体（zh-CN）、繁体（zh-TW）、英语（en）。</param>
        /// <returns>用户信息的结果。</returns>
        public UserInfoResult GetUserInfoByOpenId(string accessToken, string openid, string lang = "zh-CN")
        {
            var userInfoUrl = WeixinHelper.GetOAuthUserInfoUrl(accessToken, openid, lang);
            var result = HttpHelper.Get(userInfoUrl);
            return JsonHelper.FromJson<UserInfoResult>(result, true);
        }

        /// <summary>
        /// 检测授权的accessToken是否有效。
        /// </summary>
        /// <param name="accessToken">给定的AccessToken。</param>
        /// <param name="openid">用户唯一标识。</param>
        /// <returns>微信返回结果。</returns>
        public WeixinServiceResult IsValidAccessToken(string accessToken, string openid)
        {
            var verifyUrl = WeixinHelper.GetOAuthAccessTokenVerifyUrl(accessToken, openid);
            var result = HttpHelper.Get(verifyUrl);
            return JsonHelper.FromJson<WeixinServiceResult>(result, true);
        }

        /// <summary>
        /// 创建二维码Ticket。
        /// </summary>
        /// <param name="type">二维码类型，永久还是临时。</param>
        /// <param name="expireSeconds">过期时间，如果是永久此值无效。</param>
        /// <param name="sceneid">场景值ID。</param>
        /// <returns>二维码的ticket。</returns>
        public QRCodeTicketResult CreateQRCodeTicket(QRCodeType type, int expireSeconds, int sceneid) {
            var accessToken = GetAccessToken();
            return CreateQRCodeTicket(accessToken, type, expireSeconds, sceneid);
        }

        /// <summary>
        /// 创建二维码Ticket。
        /// </summary>
        /// <param name="accessToken">给定的AccessToken。</param>
        /// <param name="type">二维码类型，永久还是临时。</param>
        /// <param name="expireSeconds">过期时间，如果是永久此值无效。</param>
        /// <param name="sceneid">场景值ID。</param>
        /// <returns>二维码的ticket。</returns>
        public QRCodeTicketResult CreateQRCodeTicket(string accessToken, QRCodeType type, int expireSeconds, int sceneid) {
            var qrTicketUrl = WeixinHelper.GetQRCodeTicketUrl(accessToken);
            var postJson = QRCode.QRCode.CreatePostJson(type, expireSeconds, sceneid);
            var result = HttpHelper.Post(qrTicketUrl, postJson);
            return JsonHelper.FromJson<QRCodeTicketResult>(result, true);
        }

        /// <summary>
        /// 根据Ticket获取二维码。
        /// </summary>
        /// <param name="ticket">获取二维码的Ticket。</param>
        /// <returns>二维码的路径。</returns>
        public string GetQRCode(string ticket) {
            return WeixinHelper.GetQRCode(ticket);
        }

        /// <summary>
        /// 处理请求消息。
        /// </summary>
        /// <param name="request">微信端发送来的Http请求。</param>
        /// <returns>响应的消息。</returns>
        public ResponseMessageBase HandleMessage(HttpRequestBase request) {
            var timestamp = request["timestamp"];
            var nonce = request["nonce"];
            var encrypt = request["encrypt_type"];
            var msgSign = request["msg_signature"];

            if (string.IsNullOrEmpty(encrypt) || encrypt == "raw") {
                return HandleMessage(request.InputStream);
            }

            var decryptingMessage = XDocument.Load(request.InputStream);
            var decryptMessage = "";
            var returnCode = wxBizMsgCrypt.DecryptMessage(msgSign, timestamp, nonce, decryptingMessage.ToString(),
                ref decryptMessage);
            if (returnCode != 0) {
                throw new MessageEncryptingException(returnCode);
            }

            var document = XDocument.Parse(decryptMessage);
            return HandleMessage(document);
        }

        /// <summary>
        /// 根据输入获得响应。
        /// </summary>
        /// <param name="inputStream">输入的流。</param>
        /// <returns>响应的消息。</returns>
        public ResponseMessageBase HandleMessage(Stream inputStream) {
            var document = XDocument.Load(inputStream);
            return this.HandleMessage(document);
        }

        /// <summary>
        /// 处理请求消息。
        /// </summary>
        /// <param name="document">请求消息的XML文档。</param>
        /// <param name="encrypt">是否以加密模式请求。</param>
        /// <returns>响应的消息。</returns>
        public ResponseMessageBase HandleMessage(XDocument document, bool encrypt = false) {
            var requestMessage = RequestMessageBase.Create(document, encrypt);
            var responseService = messageBus.FindService(requestMessage);
            return responseService.Handle(requestMessage);
        }

        /// <summary>
        /// 根据指定的Button创建菜单。
        /// </summary>
        /// <param name="button">创建菜单对应的Button。</param>
        /// <returns>微信服务的结果。</returns>
        public WeixinServiceResult CreateMenu(Button button) {
            var accessToken = GetAccessToken();
            return CreateMenu(accessToken, button);
        }

        /// <summary>
        /// 根据指定的Button创建菜单。
        /// </summary>
        /// <param name="accessToken">给定的AccessToken。</param>
        /// <param name="button">创建菜单对应的Button。</param>
        /// <returns>微信服务的结果。</returns>
        public WeixinServiceResult CreateMenu(string accessToken, Button button) {
            var menuCreateUrl = WeixinHelper.GetMenuCreateUrl(accessToken);
            var json = button.ToJson();
            //HTB.DevFx.Log.LogService.WriteLog(json);
            var result = HttpHelper.Post(menuCreateUrl, json);
            return JsonHelper.FromJson<WeixinServiceResult>(result, true);
        }

        /// <summary>
        /// 发送模板消息。
        /// </summary>
        /// <param name="openid">发送对象的openid。</param>
        /// <param name="templateid">发送消息的模板id。</param>
        /// <param name="linkUrl">点击模板消息之后的链接。</param>
        /// <param name="topColor">模板消息顶端的颜色。</param>
        /// <param name="data">模板消息所需的数据。</param>
        /// <returns>微信服务的结果。</returns>
        public TemplateMessageResult SendTemplateMessage(string openid, string templateid, string linkUrl,
            string topColor,
            IDictionary<string, TemplateMessageData> data) {
            var accessToken = GetAccessToken();
            return SendTemplateMessage(accessToken, openid, templateid, linkUrl, topColor, data);
        }

        /// <summary>
        /// 发送模板消息。
        /// </summary>
        /// <param name="accessToken">给定的AccessToken。</param>
        /// <param name="openid">发送对象的openid。</param>
        /// <param name="templateid">发送消息的模板id。</param>
        /// <param name="linkUrl">点击模板消息之后的链接。</param>
        /// <param name="topColor">模板消息顶端的颜色。</param>
        /// <param name="data">模板消息所需的数据。</param>
        /// <returns>微信服务的结果。</returns>
        public TemplateMessageResult SendTemplateMessage(string accessToken, string openid, string templateid,
            string linkUrl,
            string topColor, IDictionary<string, TemplateMessageData> data) {
                var json = new TemplateMessage(openid, templateid, linkUrl, topColor, data).ToJson();
                var templateMessageUrl = WeixinHelper.GetTemplateMessageUrl(accessToken);
                var result = HttpHelper.Post(templateMessageUrl, json);
                return JsonHelper.FromJson<TemplateMessageResult>(result, true);
        }

        #endregion

        [Conditional("DEBUG")]
        public static void WriteLog(string content) {
            HTB.DevFx.Log.LogService.WriteLog(content);
        }
    }
}
